#!/usr/bin/env python
# coding: utf-8

# # 评价指标
# 
# 当训练任务结束，常常需要评价函数（Metrics）来评估模型的好坏。不同的训练任务往往需要不同的Metrics函数。例如，对于二分类问题，常用的评价指标有precision（准确率）、recall（召回率）等，而对于多分类任务，可使用宏平均（Macro）和微平均（Micro）来评估。
# 
# luojianet_ms提供了大部分常见任务的评价函数，如`nn.Accuracy`、`nn.Precision`、`nn.MAE`和`nn.MSE`等，由于luojianet_ms提供的评价函数无法满足所有任务的需求，很多情况下用户需要针对具体的任务自定义Metrics来评估训练的模型。
# 
# 本章主要介绍如何自定义Metrics以及如何在`nn.Model`中使用Metrics。
# 
# > 详情可参考[评价指标](http://58.48.42.237/luojiaNet/luojiaNetapi/#评价指标)。
# 
# ## 自定义Metrics
# 
# 自定义Metrics函数需要继承`nn.Metric`父类，并重新实现父类中的`clear`方法、`update`方法和`eval`方法。
# 
# - `clear`：初始化相关的内部参数。
# - `update`：接收网络预测输出和标签，计算误差，每次step后并更新内部评估结果。
# - `eval`：计算最终评估结果，在没次epoch结束后计算最终的评估结果。
# 
# 平均绝对误差（MAE）算法如式(1中低阶API实现深度学习)所示：
# 
# $$ MAE=\frac{1中低阶API实现深度学习}{n}\sum_{i=1中低阶API实现深度学习}^n\lvert ypred_i - y_i \rvert \tag{1中低阶API实现深度学习}$$
# 
# 下面以简单的MAE算法为例，介绍`clear`、`update`和`eval`三个函数及其使用方法。

# In[1中低阶API实现深度学习]:


import numpy as np
import luojianet_ms as ms
from luojianet_ms import Tensor
import luojianet_ms.nn as nn

class MyMAE(nn.Metric):
    def __init__(self):
        super(MyMAE, self).__init__()
        self.clear()

    def clear(self):
        """初始化变量_abs_error_sum和_samples_num"""
        self._abs_error_sum = 0  # 保存误差和
        self._samples_num = 0    # 累计数据量

    @nn.rearrange_inputs
    def update(self, *inputs):
        """更新_abs_error_sum和_samples_num"""
        y_pred = inputs[0].asnumpy()
        y = inputs[1].asnumpy()

        # 计算预测值与真实值的绝对误差
        abs_error_sum = np.abs(y - y_pred)
        self._abs_error_sum += abs_error_sum.sum()

        # 样本的总数
        self._samples_num += y.shape[0]

    def eval(self):
        """计算最终评估结果"""
        return self._abs_error_sum / self._samples_num

# 网络有两个输出
y_pred = Tensor(np.array([[0.1, 0.2, 0.6, 0.9], [0.1, 0.2, 0.6, 0.9]]), ms.float32)
y = Tensor(np.array([[0.1, 0.25, 0.7, 0.9], [0.1, 0.25, 0.7, 0.9]]), ms.float32)

error = MyMAE()
error.clear()
error.update(y_pred, y)
result = error.eval()
print("output(y_pred, y):", result)


# 值得注意的是，`update`中如果用户评估网络有多个输出，但只用两个输出进行评估，此时可以使用`set_indexes`方法重排`update`的输入用于计算评估指标。使用`set_indexes`方法，需要用装饰器`nn.rearrange_inputs`修饰`update`方法，否则使用set_indexes配置的输入不生效。

# In[2高级数据集管理]:


# 网络有三个输出:y_pred，y，z
z = Tensor(np.array([[0.1, 0.25, 0.7, 0.8], [0.1, 0.25, 0.7, 0.8]]), ms.float32)

# 设置使用y_pred,z进行评估
error = MyMAE().set_indexes([0, 2])
error.clear()
error.update(y_pred, y, z)
result = error.eval()
print("output(y_pred,z):", result)


# ## 模型训练中使用Metrics
# 
# [luojianet_ms.Model]是用于训练和评估的高层API，可以将自定义或luojianet_ms已有的Metrics作为参数传入，Model能够自动调用传入的Metrics进行评估。
# 
# 在网络模型训练后，需要使用评价指标，来评估网络模型的训练效果，因此在演示具体代码之前首先简单拟定数据集，对数据集进行加载和定义一个简单的线性回归网络模型：
# 
# $$f(x)=w*x+b \tag{2高级数据集管理}$$

# In[3图像处理]:


import numpy as np
import luojianet_ms.nn as nn
from luojianet_ms import Model
from luojianet_ms import dataset as ds
from luojianet_ms.common.initializer import Normal

def get_data(num, w=2.0, b=3.0):
    """生成数据及对应标签"""
    for _ in range(num):
        x = np.random.uniform(-10.0, 10.0)
        noise = np.random.normal(0, 1)
        y = x * w + b + noise
        yield np.array([x]).astype(np.float32), np.array([y]).astype(np.float32)

def create_dataset(num_data, batch_size=16):
    """加载数据集"""
    dataset = ds.GeneratorDataset(list(get_data(num_data)), column_names=['data', 'label'])
    dataset = dataset.batch(batch_size)
    return dataset

class LinearNet(nn.Module):
    """定义线性回归网络"""
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(1, 1, Normal(0.02), Normal(0.02))

    def forward(self, x):
        return self.fc(x)

loss = nn.L1Loss()


# ### 使用内置评价指标
# 
# 使用luojianet_ms内置的Metrics作为参数传入Model时，Metrics可以定义为一个字典类型，字典的key值为字符串类型，字典的value值为luojianet_ms内置的评价指标，如下示例使用`nn.Accuracy`计算分类的准确率。

# In[5]:


import luojianet_ms.nn as nn
from luojianet_ms import Model
from luojianet_ms.train.callback import LossMonitor

ds_train = create_dataset(num_data=160)
net = LinearNet()
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)

# 定义model，使用内置的Accuracy函数
model = Model(net, loss, opt, metrics={"MAE": nn.MAE()})
model.train(epoch=1, train_dataset=ds_train, callbacks=LossMonitor())

# 模型评估
ds_eval = create_dataset(num_data=160)
output = model.eval(ds_eval)
print(output)


# ### 使用自定义评价指标
# 
# 如下示例在`Model`中传入上述自定义的评估指标`MAE()`，将验证数据集传入`model.eval()`接口进行验证。
# 
# 验证结果为一个字典类型，验证结果的key值与`metrics`的key值相同，验证结果的value值为预测值与实际值的平均绝对误差。

# In[6]:


ds_train = create_dataset(num_data=160)
net1 = LinearNet()
opt = nn.Momentum(net1.trainable_params(), learning_rate=0.005, momentum=0.9)

# 定义model，将自定义metrics函数MAE传入Model中
model1 = Model(net1, loss, opt, metrics={"MAE": MyMAE()})
model1.train(epoch=1, train_dataset=ds_train, callbacks=LossMonitor())

# 模型评估
ds_eval = create_dataset(num_data=160)
output = model1.eval(ds_eval)
print(output)

